use std::{net::SocketAddr, sync::Arc};

use apikit::{
    doc::ApiDocument,
    mock::mocker::{DefalutMocker, MockOptions, Mocker},
};
use axum::{Router, extract::FromRef, routing::any};
use tokio::signal;

use crate::handler::{doc, mock};

pub struct MockServer {
    pub addr: SocketAddr,
    pub state: MockState,
}

#[derive(FromRef, Clone)]
pub struct MockState {
    pub document: Arc<ApiDocument>,
    pub mocker: Arc<dyn Mocker + Sync + Send + 'static>,
    pub options: MockOptions,
}

impl From<Arc<ApiDocument>> for MockState {
    fn from(document: Arc<ApiDocument>) -> Self {
        let mocker = Arc::new(DefalutMocker::new(document.clone()));
        let options = MockOptions {
            max_recursion: 200,
            serilaize: true,
        };
        Self {
            document,
            mocker,
            options,
        }
    }
}

async fn shutdown_signal() {
    let ctrl_c = async {
        signal::ctrl_c()
            .await
            .expect("failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("failed to install signal handler")
            .recv()
            .await;
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }
}

impl MockServer {
    pub async fn run(self) -> Result<(), anyhow::Error> {
        let listener = tokio::net::TcpListener::bind(&self.addr).await?;
        let router = Router::new();
        let router = router.route("/{*_}", any(mock));
        let router = router.route("/doc", any(doc)).with_state(self.state);
        axum::serve(
            listener,
            router.into_make_service_with_connect_info::<SocketAddr>(),
        )
        .with_graceful_shutdown(shutdown_signal())
        .await?;
        Ok(())
    }
}
